### Lab2-1 ### (base) >>> conda create --name tello_39 python=3.9 # 建立一個 tello_39 的虛擬環境,並且指定 Python 3.9 的版本 (base) >>> activate tello_39 # 啟動虛擬環境 (tello_39) >>> conda install anaconda # 為虛擬環境 tello_39 安裝 Anaconda ### Lab2-2 ### import socket # 匯入 socket 模組 import threading # 匯入 threading 模組 import time # 匯入 time 模組 class Tello(object):Number of seconds to wait for a response to a command.abort_flag """ Wrapper class to interact with the Tello drone. """ def __init__(self, local_ip, local_port, imperial = False, # __init__: 預設的方法 command_timeout = 0.3, tello_ip = '192.168.10.1', tello_port = 8889): """ Binds to the local IP/port and puts the Tello into command mode. :param local_ip: Local IP address to bind. (控制端 IP) :param local_port: Local port to bind. (控制端 Port) :param imperial: If True, speed is MPH and distance is feet. (imperial=True: 速度為英里/小時,距離為英呎) If False, speed is KPH and distance is meters. (imperial=True: 速度為公里/小時,距離為公尺) :param command_timeout: Number of seconds to wait for a response to a command. (等待指令回應的秒數) :param tello_ip: Tello IP. (無人機 IP) :param tello_port: Tello port. (無人機 Port) """ self.abort_flag = False # 中止旗標 self.command_timeout = command_timeout # 指令回應時間 self.imperial = imperial # 單位模式 self.response = None # 無人機回應訊息 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 建立 socket,socket.AF_INET: 基地台模式,socket.SOCK_DGRAM: 採用 UDP 協定 self.tello_address = (tello_ip, tello_port) # 無人機的網路位址 self.last_height = 0 # 無人機降落時的高度 self.socket.bind((local_ip, local_port)) # 控制端的網路位址 # thread for receiving cmd ack self.receive_thread = threading.Thread(target = self._receive_thread) self.receive_thread.daemon = True self.receive_thread.start() self.socket.sendto(b'command', self.tello_address) # 發送 SDK 'command' 給 tello print('sent: command') # 螢幕顯示 def __del__(self): # __del__: 預設的方法,關閉程式時,自動結束 socket """ Closes the local socket. :return: None. """ self.socket.close() def _receive_thread(self): # 接收無人機的回應 """ Listen to responses from the Tello. Runs as a thread, sets self.response to whatever the Tello last returned. :return: None. """ while True: try: self.response, _ = self.socket.recvfrom(3000) except socket.error as exc: print(f'Caught exception socket.error : {exc}') def send_command(self, command): # 向無人機發送指令 """ Send a command to the Tello and wait for a response. :param command: Command to send. :return: Response from Tello. """ print(f'>> send cmd: {command}') self.abort_flag = False timer = threading.Timer(self.command_timeout, self.set_abort_flag) self.socket.sendto(command.encode('utf-8'), self.tello_address) timer.start() # 啟動 timer while self.response is None: # self.response = None,等待無人機的回應 if self.abort_flag is True: # 若 self.abort_flag = True,代表有發送指令,但沒有收到回應,則離開等待 break timer.cancel() # 取消 timer if self.response is None: response = 'none_response' else: response = self.response.decode('utf-8') self.response = None # 下次要重新發送指令,需要初始物件屬性 return response # 傳回無人機的回應 def set_abort_flag(self): """ Sets self.abort_flag to True. Used by the timer in Tello.send_command() to indicate to that a response timeout has occurred. :return: None. """ self.abort_flag = True def takeoff(self): """ Initiates take-off. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.send_command('takeoff') def land(self): """ Initiates landing. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.send_command('land') ### Lab2-3 ### >>> drone = Tello(local_ip = '192.168.10.2', local_port = 8889) >>> drone.takeoff() >>> drone.land() ### Lab2-4 (2-2/tello.py) ### import socket import threading import time class Tello(object): """ Wrapper class to interact with the Tello drone. """ def __init__(self, local_ip, local_port, imperial = False, command_timeout = 0.3, tello_ip = '192.168.10.1', tello_port = 8889): """ Binds to the local IP/port and puts the Tello into command mode. :param local_ip: Local IP address to bind. :param local_port: Local port to bind. :param imperial: If True, speed is MPH and distance is feet. If False, speed is KPH and distance is meters. :param command_timeout: Number of seconds to wait for a response to a command. :param tello_ip: Tello IP. :param tello_port: Tello port. """ self.abort_flag = False self.command_timeout = command_timeout self.imperial = imperial self.response = None self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.tello_address = (tello_ip, tello_port) self.last_height = 0 self.socket.bind((local_ip, local_port)) # thread for receiving cmd ack self.receive_thread = threading.Thread(target = self._receive_thread) self.receive_thread.daemon = True self.receive_thread.start() self.socket.sendto(b'command', self.tello_address) print ('sent: command') def __del__(self): """ Closes the local socket. :return: None. """ self.socket.close() def _receive_thread(self): """ Listen to responses from the Tello. Runs as a thread, sets self.response to whatever the Tello last returned. :return: None. """ while True: try: self.response, _ = self.socket.recvfrom(3000) except socket.error as exc: print(f'Caught exception socket.error : {exc}') def send_command(self, command): """ Send a command to the Tello and wait for a response. :param command: Command to send. :return: Response from Tello. """ print(f'>> send cmd: {command}') self.abort_flag = False timer = threading.Timer(self.command_timeout, self.set_abort_flag) self.socket.sendto(command.encode('utf-8'), self.tello_address) timer.start() while self.response is None: if self.abort_flag is True: break timer.cancel() if self.response is None: response = 'none_response' else: response = self.response.decode('utf-8') self.response = None return response def set_abort_flag(self): """ Sets self.abort_flag to True. Used by the timer in Tello.send_command() to indicate to that a response timeout has occurred. :return: None. """ self.abort_flag = True def takeoff(self): """ Initiates take-off. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.send_command('takeoff') def set_speed(self, speed): """ Sets speed. This method expects KPH or MPH. The Tello API expects speeds from 1 to 100 centimeters/second. Metric: .1 to 3.6 KPH Imperial: .1 to 2.2 MPH :param speed: Speed. :return: Response from Tello, 'OK' or 'FALSE'. """ speed = float(speed) if self.imperial is True: speed = int(round(speed * 44.704)) else: speed = int(round(speed * 27.7778)) return self.send_command(f'speed {speed}') def rotate_cw(self, degrees): """ Rotates clockwise. :param degrees: Degrees to rotate, 1 to 360. :return:Response from Tello, 'OK' or 'FALSE'. """ return self.send_command(f'cw {degrees}') def rotate_ccw(self, degrees): """ Rotates counter-clockwise. :param degrees: Degrees to rotate, 1 to 360. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.send_command(f'ccw {degrees}') def flip(self, direction): """ Flips. :param direction: Direction to flip, 'l', 'r', 'f', 'b'. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.send_command(f'flip {direction}') def get_response(self): """ Returns response of tello. :return: Response of tello. """ response = self.response return response def get_height(self): """ Returns height(dm) of tello. :return: Height(dm) of tello. """ height = self.send_command('height?') height = str(height) height = filter(str.isdigit, height) try: height = int(height) self.last_height = height except: height = self.last_height pass return height def get_battery(self): """ Returns percent battery life remaining. :return: Percent battery life remaining. """ battery = self.send_command('battery?') try: battery = int(battery) except: pass return battery def get_flight_time(self): """ Returns the number of seconds elapsed during flight. :return: Seconds elapsed during flight. """ flight_time = self.send_command('time?') try: flight_time = int(flight_time) except: pass return flight_time def get_speed(self): """ Returns the current speed. :return: Current speed in KPH or MPH. """ speed = self.send_command('speed?') try: speed = float(speed) if self.imperial is True: speed = round((speed / 44.704), 1) else: speed = round((speed / 27.7778), 1) except: pass return speed def land(self): """ Initiates landing. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.send_command('land') def move(self, direction, distance): """ Moves in a direction for a distance. This method expects meters or feet. The Tello API expects distances from 20 to 500 centimeters. Metric: .02 to 5 meters Imperial: .7 to 16.4 feet :param direction: Direction to move, 'forward', 'back', 'right' or 'left'. :param distance: Distance to move. :return: Response from Tello, 'OK' or 'FALSE'. """ distance = float(distance) if self.imperial is True: distance = int(round(distance * 30.48)) else: distance = int(round(distance * 100)) return self.send_command(f'{direction} {distance}') def move_backward(self, distance): """ Moves backward for a distance. See comments for Tello.move(). :param distance: Distance to move. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.move('back', distance) def move_down(self, distance): """ Moves down for a distance. See comments for Tello.move(). :param distance: Distance to move. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.move('down', distance) def move_forward(self, distance): """ Moves forward for a distance. See comments for Tello.move(). :param distance: Distance to move. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.move('forward', distance) def move_left(self, distance): """ Moves left for a distance. See comments for Tello.move(). :param distance: Distance to move. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.move('left', distance) def move_right(self, distance): """ Moves right for a distance. See comments for Tello.move(). :param distance: Distance to move. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.move('right', distance) def move_up(self, distance): """ Moves up for a distance. See comments for Tello.move(). :param distance: Distance to move. :return: Response from Tello, 'OK' or 'FALSE'. """ return self.move('up', distance) ### Lab2-5 (2-2/look4corner.py) ### import tello from time import sleep drone = tello.Tello(local_ip = '192.168.10.2', local_port = 8889) drone.takeoff() sleep(5) for i in range(4): drone.move_forward(1) sleep(5) drone.rotate_cw(90) sleep(5) drone.land() ### Lab2-6 (2-3.py) ### import tkinter # 匯入 tkinter 模組 root = tkinter.Tk() # 建立 Tk 視窗物件 root.mainloop() # 產生視窗,也可以說 mainloop() 為更新 GUI 的驅動程式 ### Lab2-7 (2-4.py) ### import tkinter root = tkinter.Tk() root.title('第一個視窗') # 設定視窗標題 root.geometry('800x600') # 設定視窗大小 root.mainloop() ### Lab2-8 (2-5.py) ### import tkinter root = tkinter.Tk() root.title('第一個標籤') root.geometry('800x600') label = tkinter.Label(root, text = '標籤的字串', font = ('System', 24)) # 建立一個標籤物件,並初始文字與字型 label.place(x = 200, y = 100) # 配置標籤與位置 root.mainloop() ### Lab2-9 (2-6.py) ### import tkinter import tkinter.font # 匯入 tkinter 模組的 font 類別 root = tkinter.Tk() print(tkinter.font.families()) # 顯示系統安裝的字體 ### Lab2-10 (2-7.py) ### import tkinter root = tkinter.Tk() root.title('第一個按鈕') root.geometry('800x600') button = tkinter.Button(root, text = '按鈕的字串', font = ('Times New Roman', 24)) # 建立一個 Button 物件,並初始文字與字型 button.place(x = 200, y = 100) # 配置 Button 與位置 root.mainloop() ### Lab2-11 (2-8.py) ### import tkinter def click_btn(): # 定義點選按鈕的處理 button['text'] = '點選按鈕了' root = tkinter.Tk() root.title('第一個按鈕') root.geometry('800x600') button = tkinter.Button(root, text = '請點選按鈕', font = ('Times New Roman', 24), command = click_btn) # 點選按鈕時候執行該函式 click_btn button.place(x = 200, y = 100) root.mainloop() ### Lab2-12 (2-9.py) ### import tkinter root = tkinter.Tk() root.title('第一張畫布') canvas = tkinter.Canvas(root, width = 400, height = 600, bg = 'skyblue') # 建立一個畫布物件,並初始大小與背景色 canvas.pack() # # 配置畫布 root.mainloop() ### Lab2-13 (2-10.py) ### import tkinter root = tkinter.Tk() root.title('第一次顯示圖片') canvas = tkinter.Canvas(root, width = 400, height = 600) canvas.pack() gazou = tkinter.PhotoImage(file = 'images/iroha.png') # 建立 PhotoImage 的物件,參數為圖片路徑 canvas.create_image(200, 300, image = gazou) # 配置圖片,X 座標與 Y 座標是的中心點。 root.mainloop() ### Lab2-14 (2-11.py) ### import tkinter root = tkinter.Tk() root.title('抽籤遊戲') root.resizable(False, False) # 讓視窗長寬都不可以更動大小 canvas = tkinter.Canvas(root, width = 800, height = 600) canvas.pack() gazou = tkinter.PhotoImage(file = 'images/miko.png') canvas.create_image(400, 300, image = gazou) root.mainloop() ### Lab2-15 (2-12.py) ### import tkinter root = tkinter.Tk() root.title('抽籤遊戲') root.resizable(False, False) canvas = tkinter.Canvas(root, width = 800, height = 600) canvas.pack() gazou = tkinter.PhotoImage(file = 'images/miko.png') canvas.create_image(400, 300, image = gazou) label = tkinter.Label(root, text = '??', font = ('Times New Roman', 120), bg = 'white') # 建立標籤 label.place(x = 380, y = 60) button = tkinter.Button(root, text = '抽籤', font = ('Times New Roman', 36), fg = 'skyblue') # 建立 Button button.place(x = 360, y = 400) root.mainloop() ### Lab2-16 (2-13.py) ### import tkinter import random # 匯入 random 模組 def click_btn(): label['text'] = random.choice(['大吉', '中吉', '小吉', ' 凶 ']) # 使用 random.choice() 隨機取 list 的元素 label.update() # 手動更新 label root = tkinter.Tk() root.title('抽籤遊戲') root.resizable(False, False) canvas = tkinter.Canvas(root, width = 800, height = 600) canvas.pack() gazou = tkinter.PhotoImage(file = 'images/miko.png') canvas.create_image(400, 300, image = gazou) label = tkinter.Label(root, text = '??', font = ('Times New Roman', 120), bg = 'white') label.place(x = 380, y = 60) button = tkinter.Button(root, text = '抽籤', font = ('Times New Roman', 36), command = click_btn, fg = 'skyblue') button.place(x = 360, y = 400) root.mainloop() ### Lab2-17 (2-14.py) ### import tkinter tmr = 0 def count_up(): global tmr # 允許變更全域變數 tmr tmr = tmr + 1 label['text'] = tmr root.after(1000, count_up) root = tkinter.Tk() label = tkinter.Label(font = ('Times New Roman', 80)) label.pack() root.after(1000, count_up) root.mainloop() ### Lab2-18 ### import tkinter tmr = 0 def count_up(): tmr = tmr + 1 # 錯誤,因為函式 count_up() 會先執行,但變數 tmr 並沒有宣告 label['text'] = tmr root.after(1000, count_up) root = tkinter.Tk() label = tkinter.Label(font = ('Times New Roman', 80)) label.pack() root.after(1000, count_up) root.mainloop() ### Lab2-19 ### import tkinter def count_up(): tmr = 0 tmr = tmr + 1 label['text'] = tmr root.after(1000, count_up) root = tkinter.Tk() label = tkinter.Label(font = ('Times New Roman', 80)) label.pack() root.after(1000, count_up) root.mainloop() ### Lab2-20 ### word1 = 'You' word2 = 'me' def get_new_word(): print(word1 + '&' + word2) get_new_word() ### Lab2-21 (2-15.py) ### import tkinter key = 0 def key_down(e): global key key = e.keycode print('KEY:' + str(key)) root = tkinter.Tk() root.title('取得鍵碼') root.bind('', key_down) # 綁定按鍵按下的函式 root.mainloop() ### Lab2-22 (2-16.py) ### import tkinter key = 0 def key_down(e): global key key = e.keycode def main_proc(): label['text'] = key root.after(100, main_proc) root = tkinter.Tk() root.title('即時按鍵輸入處理') root.bind('', key_down) label = tkinter.Label(font = ('Times New Roman', 80)) label.pack() main_proc() root.mainloop() ### Lab2-23 (2-17.py) ### import tkinter key = '' def key_down(e): global key key = e.keysym def main_proc(): label['text'] = key root.after(100, main_proc) root = tkinter.Tk() root.title('即時按鍵輸入處理') root.bind('', key_down) label = tkinter.Label(font = ('Times New Roman', 80)) label.pack() main_proc() root.mainloop() ### Lab2-24 (2-18.py) ### import tkinter key = '' def key_down(e): global key key = e.keysym def key_up(e): global key key = "" cx = 400 cy = 300 def main_proc(): global cx, cy if key == 'Up': cy = cy - 20 if key == 'Down': cy = cy + 20 if key == 'Left': cx = cx - 20 if key == 'Right': cx = cx + 20 canvas.coords('MYCHR', cx, cy) root.after(100, main_proc) root = tkinter.Tk() root.title('移動角色') root.bind('', key_down) root.bind('', key_up) canvas = tkinter.Canvas(width = 800, height = 600, bg = 'lightgreen') canvas.pack() img = tkinter.PhotoImage(file = 'images/mimi.png') canvas.create_image(cx, cy, image = img, tag = 'MYCHR') main_proc() root.mainloop() ### Lab2-25 (2-19/ui.py) ### import tkinter as tki # 匯入 tkinter 模組 from tkinter import Toplevel # 匯入 tkinter 模組的 Toplevel 類別 import threading # 匯入 threading 模組 import time class TelloUI(object): # 建立 TelloUI 類別 def __init__(self, tello): # 需要傳入 tello 物件當參數 self.tello = tello # 建立 tello 物件 self.thread = None self.stopEvent = None self.distance = 0.3 # 預設飛行的單位為 0.3 公尺 self.quit_waiting_flag = False self.root = tki.Tk() # 建立視窗物件 self.panel = None self.btn_openPanel = tki.Button(self.root, text = 'Open Command Panel', relief = 'raised', command = self.openCmdWindow) # 建立 Open Command Panel 的按鍵 self.btn_openPanel.pack(side = 'bottom', fill = 'both', expand = 'yes', padx = 10, pady = 5) # 配置 Open Command Panel 的按鍵 self.stopEvent = threading.Event() self.root.wm_title('TELLO Controller') self.root.wm_protocol('WM_DELETE_WINDOW', self.on_close) self.sending_command_thread = threading.Thread(target = self._sendingCommand) def _sendingCommand(self): while True: self.tello.send_command('command') time.sleep(5) def openCmdWindow(self): panel = Toplevel(self.root) panel.wm_title('Command Panel') text1 = tki.Label(panel, text = 'W - Move Tello Up\t\t\tArrow Up - Move Tello Forward\n' 'S - Move Tello Down\t\t\tArrow Down - Move Tello Backward\n' 'A - Rotate Tello Counter-Clockwise\tArrow Left - Move Tello Left\n' 'D - Rotate Tello Clockwise\t\tArrow Right - Move Tello Right', justify = 'left') text1.pack(side = 'top') self.btn_landing = tki.Button(panel, text = 'Land', relief = 'raised', command = self.telloLanding) self.btn_landing.pack(side = 'bottom', fill = 'both', expand = 'yes', padx = 10, pady = 5) self.btn_takeoff = tki.Button(panel, text = 'Takeoff', relief = 'raised', command = self.telloTakeOff) self.btn_takeoff.pack(side = 'bottom', fill = 'both', expand = 'yes', padx = 10, pady = 5) self.tmp_f = tki.Frame(panel, width = 100, height = 2) self.tmp_f.bind('', self.on_keypress_up) self.tmp_f.pack(side = 'bottom') self.tmp_f.focus_set() def telloTakeOff(self): return self.tello.takeoff() def telloLanding(self): return self.tello.land() def telloMoveForward(self, distance): return self.tello.move_forward(distance) def on_keypress_up(self, event): print(f'forward {self.distance} m') self.telloMoveForward(self.distance) def on_close(self): print('[INFO] closing...') self.stopEvent.set() del self.tello self.root.quit() ### Lab2-26 (2-19/app.py) ### import tello from ui import TelloUI def main(): drone = tello.Tello('192.168.10.2', 8889) vplayer = TelloUI(drone) # 建立 UI 物件 vplayer.root.mainloop() # 建立視窗 if __name__ == '__main__': main() # 呼叫 main()